 package fr.doume.jna.sspi;

//tomcat 5.5
//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;

//tomcat 6
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import fr.doume.jna.authenticator.Parameters;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.platform.win32.Sspi;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Kernel32Util;
import com.sun.jna.platform.win32.W32Errors;
import com.sun.jna.platform.win32.Secur32;
import fr.doume.jna.jna.Advapi32bis;
import com.sun.jna.platform.win32.Win32Exception;
import fr.doume.jna.jna.Sspibis.SecBufferDesc;
import fr.doume.jna.jna.Sspibis.SecBuffer;
import com.sun.jna.platform.win32.Sspi.TimeStamp;
import fr.doume.jna.jna.Sspibis.CtxtHandle;
import fr.doume.jna.jna.Sspibis.SecHandle;
import fr.doume.jna.jna.Sspibis.CredHandle;
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;

import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.WinBase;


import fr.doume.jna.authenticator.Parameters;
import fr.doume.jna.authenticator.AuthenticationContext;
import fr.doume.jna.sspi.TranslationRolesIntoSids;

import fr.doume.jna.sspi.SSPException;

import fr.doume.jna.jna.Secur32bis;
import fr.doume.jna.jna.Secur32bis.SecPkgInfoBis;

import fr.doume.jna.sspi.WindowsContext;


public class AuthenticationUtil
{

  private CredHandle hCredential = null;
  private long dwUpperCredential = 0;
  private long dwLowerCredential = 0;
  private CtxtHandle hContext = null;
  private long dwUpperContext = 0;
  private long dwLowerContext = 0; 
  
  private WindowsContext windowscontext = null;
  
  private boolean continue_needed = false;
  private boolean isestablished = false;
  private boolean isfailed = true; 
  private String SSP = "Negotiate";
  private AuthenticationContext authenticationContext;
  private TranslationRolesIntoSids translation;
  private Parameters p;
  private String username = null;
  private String sspname = null;
  private boolean [] rolesInAccessToken = null;
  private boolean userauthenticated = false;
  
  private String remoteaddress = null;
  
  
  private final static int SECPKG_ATTR_NAMES = 1;
  private final static int SECPKG_ATTR_PACKAGE_INFO = 10;
  
 	private static Log log = LogFactory.getLog(AuthenticationUtil.class);


  private boolean isdisposed = false;
  
  public AuthenticationUtil( AuthenticationContext ac)
  {
    authenticationContext = ac;
    p = ac.getParameters();
    if (p.isNTLMonly() ) SSP = "NTLM";
    else SSP = "Negotiate";
    translation = ac.getTranslationRolesIntoSids();
  }

  public AuthenticationUtil( AuthenticationContext ac, String remoteAddr)
  {
    authenticationContext = ac;
    p = ac.getParameters();
    if (p.isNTLMonly() ) SSP = "NTLM";
    else SSP = "Negotiate";
    //hCredential = new CredHandle();
    //hContext = new CtxtHandle();
    translation = ac.getTranslationRolesIntoSids();
    remoteaddress = remoteAddr;
  }

  public WindowsContext getWindowsContext()
  {
    if (windowscontext == null)
    {
      return new WindowsContext( dwLowerCredential, dwUpperCredential, dwLowerContext, dwUpperContext);
    }
    else
    {
      windowscontext.dwLowerCredential = dwLowerCredential;
      windowscontext.dwUpperCredential = dwUpperCredential;
      windowscontext.dwLowerContext = dwLowerContext; 
      windowscontext.dwUpperContext = dwUpperContext;
      return windowscontext;
    } 
  }

  public void setWindowsContext(WindowsContext winctx)
  {
    windowscontext = winctx;
    dwLowerCredential = winctx.dwLowerCredential;
    dwUpperCredential = winctx.dwUpperCredential;
    dwLowerContext = winctx.dwLowerContext; 
    dwUpperContext = winctx.dwUpperContext;
  }


  public boolean isFailed()
  {
    return ( isfailed || !userauthenticated );
  }
  
  public boolean isEstablished()
  {
    return ( isestablished && userauthenticated );
  }
  
  public boolean isContinueNedded()
  {
    return continue_needed;
  }
  
  
  public String getClientName()
  {
    return username;
  }
  
  public String getSSPName()
  {
    return sspname;
  }
  
  private boolean IsUserAuthenticated( HANDLE hToken)
  {
    byte[] sid_bytes = {1, 1,
                        0,0,0,0,0,
                        5,11,
                        0,0,0};
    if ( IsInAccessToken( sid_bytes, hToken) ) return true;
    else
    {
      if (log.isDebugEnabled() ) log.debug("user is not an Authenticated User!!!"); 
      return false;
    }
  }
  
  private boolean IsInAccessToken(byte[] sid_bytes, HANDLE hToken)
  {

    IntByReference isMember= new IntByReference(0);
    if (sid_bytes == null || sid_bytes.length == 0) return false;
    //PSID sid = new PSID(sid_bytes);
    
    //
    if ( ! Advapi32bis.INSTANCE.CheckTokenMembership( hToken, sid_bytes, isMember)) 
    {
      if (log.isDebugEnabled()) log.debug("CheckTokenMembership returns false");
      return false;
    }
    
    if (isMember.getValue() != 0 ) return true;
    else return false;            
  }
  
  
  public boolean[] RolesInAccessToken( byte[][] sids_array )
  {
    return rolesInAccessToken;
  }
  
  public boolean isAnAuthenticatedUser()
  {
    return userauthenticated;
  }
  
  private boolean isAuthenticatedUserWithLogin(String name, String domain, String password)
  {
    HANDLE h = doLogin(name, domain, password);
    if ( IsUserAuthenticated(h) ) userauthenticated = true;
    else userauthenticated = false;
    
    if (! Kernel32.INSTANCE.CloseHandle(h))
    {
      logLastErrorAndThrowException();
    }
    return userauthenticated;      
  }

  private boolean[] getRolesInAccessTokenWithLogin(byte[][] sids_array, String name, String domain, String password)
  {
    if ( sids_array == null ) return null;
    boolean[] inAccessToken = new boolean[sids_array.length];
    
    HANDLE h = doLogin(name, domain, password);
    
    for (int i = 0; i < sids_array.length; i++)
    {
      inAccessToken[i] = IsInAccessToken( sids_array[i], h );
    }
    if ( IsUserAuthenticated(h) ) userauthenticated = true;
    else userauthenticated = false;
    
    if (! Kernel32.INSTANCE.CloseHandle(h))
    {
      logLastErrorAndThrowException();
    }

    return inAccessToken;  
  }

  
  private HANDLE doLogin(String name, String domain, String password)
  {
    HANDLEByReference phToken = new HANDLEByReference();
    if (!Advapi32.INSTANCE.LogonUser( name, domain, password ,
                WinBase.LOGON32_LOGON_NETWORK, WinBase.LOGON32_PROVIDER_DEFAULT,
                phToken)
       )
    {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
    return (phToken.getValue());
  }

  public void login(String name, String domain, String password)
  throws SSPException
  {
    try
    {
      username = name;

      if ( p.areGroupsInAd() )
      {
        byte[][] sids_array = translation.getSids();
        rolesInAccessToken =getRolesInAccessTokenWithLogin(sids_array, name, domain, password);
        if ( !isAnAuthenticatedUser() )
        {
          isestablished = false;
          isfailed = true;
          throw new SSPException(" user is not an authenticated user!!");               
        }
      }
      else
      {
        if ( !isAuthenticatedUserWithLogin(name, domain,password) )
        {
          isestablished = false;
          isfailed = true;
          throw new SSPException(" user is not an authenticated user!!");
        }
      }
        
      if (log.isDebugEnabled()) log.debug("Context Is Establhished ");

      isestablished = true;
      isfailed = false;
    }
    catch(Exception e)
    {
      if (log.isDebugEnabled())
      {
        log.debug(e.getMessage());
        e.printStackTrace();
      }
      
      isestablished = false;
      isfailed = true;
      String s = "Exception : "  + e.getClass().getName() + " : " + e.getMessage();

      throw new SSPException(s);
    }
  }
  private void logReturnCode(int code)
  {
    if (log.isDebugEnabled())
    {
      log.debug("return code :" + code);
      HRESULT hr = W32Errors.HRESULT_FROM_WIN32(code);
      String msg = Kernel32Util.formatMessageFromHR(hr);
      log.debug(msg + " : " + code);
    }
  }
  
  private void logLastErrorAndThrowException()
  {
    int code = Kernel32.INSTANCE.GetLastError();
    if (log.isDebugEnabled())
    {
      logReturnCode(code);
    }
    throw new Win32Exception(code); 
  }
  
  public boolean isDisposed()
  {  
    return isdisposed;
  }

  private boolean isContextInvalid()
  {
    return ( ( dwUpperContext == 0  && dwLowerContext ==0 )  || ( dwUpperContext == -1 && dwLowerContext == -1) );
  }
  
  private boolean isCredentialInvalid()
  {
    return ( ( dwUpperCredential == 0  && dwLowerCredential ==0 ) || (dwUpperCredential == -1 && dwLowerCredential == -1) );
  }


  public byte[] getContext(byte[] token)
  throws SSPException
  {
    if (log.isDebugEnabled())  log.debug(
      "dwLowerCredential: " + dwLowerCredential +" ,dwUpperCredential: " + dwUpperCredential +
      " , dwLowerContext: " + dwLowerContext +" ,dwUpperContext: " + dwUpperContext); 
 
    hCredential = new CredHandle( dwLowerCredential, dwUpperCredential);
    hContext = new CtxtHandle( dwLowerContext, dwUpperContext);
     
    getCredential();
    if (log.isDebugEnabled() )  log.debug("after getCredential");
    byte [] tokentosend = getInternalContext( token );
    
    if (log.isDebugEnabled() )
    {
      log.debug("after getContext");
      if (tokentosend == null) log.debug(" tokentosend null");
      else log.debug("lentgh of tokentosend : " + tokentosend.length) ;  
    } 
    return tokentosend;
  }
  
  
  private void getCredential()
  throws SSPException
  {
    int rc;
    continue_needed = false;
    isestablished = false;
    isfailed = true;

    try
    {
    //precondition: hCredential is null
      if ( ! hCredential.isNull() ) return;
      if (    (! hCredential.isInvalid())  ||   ( ! hContext.isInvalid() )   ) 
        throw (new SSPException("precondition of getCredential is not set!!"));
        
      TimeStamp expiryServer = new TimeStamp();
      if (log.isDebugEnabled()) log.debug("before Secur32.INSTANCE.AcquireCredentialsHandle");
      rc = Secur32bis.INSTANCE.AcquireCredentialsHandle(
                          null,
                          SSP,
                          new NativeLong(Sspi.SECPKG_CRED_INBOUND), 
                          null, null, null, null,
                          hCredential, 
                          expiryServer);

      if (log.isDebugEnabled())
      {
        log.debug("after Secur32.INSTANCE.AcquireCredentialsHandle");  
        if (hCredential.isInvalid()) log.debug("invalid hcredential");
        else log.debug("hcredential non nul");
      }
      
      if (rc != W32Errors.SEC_E_OK )
      { 
        if (log.isDebugEnabled())
        {
          log.debug( "Error AcquireCredentialsHandle :" + rc );
        }
        dispose();
        logLastErrorAndThrowException();
      }
      else //no error
      {
        isfailed = false;
        log.debug("End getCredential");
      }  
    } 
    catch (Exception e)
    {
      dispose();
      String s = "Exception : "  + e.getClass().getName() + " : " + e.getMessage();
      if (log.isDebugEnabled())
      {
        log.debug("Erreur!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        log.debug(s);
        e.printStackTrace();
      }
      throw new SSPException(s);      

    }
  }
  
  private byte[] getInternalContext(byte[] token)
  throws SSPException
  {
    byte[] tokenServer = null;
    boolean hasToDispose = false;
    
    if (log.isDebugEnabled()) log.debug("Begin getContext");

    continue_needed = false;
    isestablished = false;
    isfailed = true;
    
    try
    {
      if (hCredential.isInvalid())  //precondition
      {
          throw new SSPException("hCredential is invalid");      
      }
      else
      {
        if (log.isDebugEnabled()) log.debug("dwLowerCredential: " + hCredential.getLower() +" ,dwUpperCredential: " + hCredential.getUpper());
      }
      SecBufferDesc sbdServer = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
      if (log.isDebugEnabled()) log.debug("Begin getContext after sbdserver");

      SecBufferDesc sbdClient = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, token);
      if (log.isDebugEnabled()) log.debug("Begin getContext after sbdclient");
      //NativeLongByReference CtxAttrsServer = new NativeLongByReference(new NativeLong(0));
      
      NativeLongByReference CtxAttrsServer = new NativeLongByReference();
      TimeStamp expiryServerCtx = new TimeStamp();
      
      if (log.isDebugEnabled())log.debug("token length : " + token.length); 
      if (log.isDebugEnabled())log.debug("before Secur32.INSTANCE.AcceptSecurityContext"); 
      
      int rc = Secur32bis.INSTANCE.AcceptSecurityContext(
                        hCredential, 
                        hContext.isInvalid() ? null : hContext,
                        sbdClient, 
                        new NativeLong(Sspi.ISC_REQ_CONNECTION), 
                        new NativeLong(Sspi.SECURITY_NATIVE_DREP), 
                        hContext,
                        sbdServer, 
                        CtxAttrsServer, 
                        expiryServerCtx);
      if (log.isDebugEnabled()) log.debug("after Secur32.INSTANCE.AcceptSecurityContext : " + rc);                        
                        
      if( rc != W32Errors.SEC_I_CONTINUE_NEEDED && rc != W32Errors.SEC_E_OK )  //error
      {
        continue_needed = false;
        isestablished = false;
        isfailed = true;
        
        if (log.isDebugEnabled())
        {
          log.debug("Error AcceptSecurityContext : " + rc);
        }
        
        logLastErrorAndThrowException();  //throw new Win32Exception 
      }
      else // no error
      {
        if (rc == W32Errors.SEC_E_OK) //authenticated
        {
          continue_needed = false;
          hasToDispose = true;
          
          try
          {
            username = getUserName();
            sspname = getSecurityServiceProviderName();

            if ( p.areGroupsInAd() )
            {
              byte[][] sids_array = translation.getSids();
              rolesInAccessToken =getRolesInAccessToken(sids_array);
              if ( !isAnAuthenticatedUser() )
              {
                isestablished = false;
                isfailed = true;
                throw new SSPException(" user is not an authenticated user!!");               
              }
            }
            else
            {
              if ( isLocalGuest() ) //is not an authenticated user
              {
                isestablished = false;
                isfailed = true;
                throw new SSPException(" user is not an authenticated user!!");
              }
            }
              
            if (log.isDebugEnabled()) log.debug("Context Is Establhished ");

            isestablished = true;
            isfailed = false;
          }
          catch(Exception e)
          {
            if (log.isDebugEnabled())
            {
              log.debug(e.getMessage());
              e.printStackTrace();
            }
            
            isestablished = false;
            isfailed = true;
            throw e;
          }
          
          if (log.isDebugEnabled()) log.debug("Is Establhished AcceptSecurityContext");

        }
        else  //rc == W32Errors.SEC_I_CONTINUE_NEEDED (NTLM)
        {
          continue_needed = true;
          isestablished = false;
          isfailed = false;
          if (log.isDebugEnabled()) log.debug("Must be continued AcceptSecurityContext");
        }
        
        //SecBuffer secBuffer = sbdServer.pBuffers[0];
        SecBuffer secBuffer = sbdServer.pBuffers;
        long countBytesReturnFromAccept = secBuffer.cbBuffer.longValue();
        if ( countBytesReturnFromAccept != 0 ) 
        {
          if (log.isDebugEnabled()) log.debug(" length returned : " + countBytesReturnFromAccept);
        }
        tokenServer = secBuffer.getBytes();
      }
      if (log.isDebugEnabled()) log.debug(" end of getContext");
    }
    catch (Exception e)
    {
      continue_needed = false;
      isestablished = false;
      isfailed = true;     

      dispose();
      String s = "Exception : "  + e.getClass().getName() + " : " + e.getMessage();
      if (log.isDebugEnabled())
      {
        log.debug(s);
        e.printStackTrace();
      }
      throw new SSPException(s);      
    }

    if (hasToDispose) dispose(); //isFailed or isAuthenticated
    else saveHandle(); //another round bettween the client and the server is needed 
  
    return tokenServer;
    
  }
  
  /**/
  private String getUserName()
  {
    String nameClient = null;
       
    PointerByReference pp = new PointerByReference();
    
    int rc = Secur32bis.INSTANCE.QueryContextAttributes( hContext, SECPKG_ATTR_NAMES, pp);
    if (rc != 0) logLastErrorAndThrowException();

    Pointer p = pp.getValue();

    // getString(0, true): create a copy from the memory pointed by p, at the offset 0, a native Unicode String
    nameClient = p.getString(0, true);
    if (log.isDebugEnabled()) log.debug(nameClient);

    if (log.isDebugEnabled()) log.debug("Before Secur32bis.INSTANCE.FreeContextBuffer(p)");   
    rc = Secur32bis.INSTANCE.FreeContextBuffer(p);    
    if (log.isDebugEnabled())
    {
      log.debug("FreeContextBuffer : " + rc);
    } 
    if (rc != 0) logLastErrorAndThrowException(); //throw new Win32Exception(rc);    
    
    return nameClient;

  }

  /**/
  private String getSecurityServiceProviderName()
  {
    String name = null;
       
    PointerByReference pp = new PointerByReference();
    
    int rc = Secur32bis.INSTANCE.QueryContextAttributes( hContext, SECPKG_ATTR_PACKAGE_INFO, pp);
    if (rc != 0) logLastErrorAndThrowException();

    Pointer p = pp.getValue();
    
    //Map the memory to the native structure secPkgInfoBis
    SecPkgInfoBis secPkgInfoBis = new SecPkgInfoBis(p);

    //Read the field pName from the native memory to the java field pName
    secPkgInfoBis.readField("pName");
    
    if (log.isDebugEnabled()) log.debug("Before getName of the SSP");

    // getString(0, true): create a copy from the memory pointed by p, at the offset 0 (true => a native Unicode String )   
    name = secPkgInfoBis.pName.getString(0, true);
    if (log.isDebugEnabled()) log.debug(name);

    if (log.isDebugEnabled()) log.debug("Before Secur32bis.INSTANCE.FreeContextBuffer(p)");   
    rc = Secur32bis.INSTANCE.FreeContextBuffer(p);    
    if (log.isDebugEnabled())
    {
      log.debug("FreeContextBuffer : " + rc);
    } 
    if (rc != 0) logLastErrorAndThrowException(); //throw new Win32Exception(rc);    
    
    return name;
  }

  private boolean[] getRolesInAccessToken(byte[][] sids_array)
  {
    if ( sids_array == null ) return null;
    boolean[] inAccessToken = new boolean[sids_array.length];
    
    HANDLE h = getAccessToken();
    for (int i = 0; i < sids_array.length; i++)
    {
      inAccessToken[i] = IsInAccessToken( sids_array[i], h );
    }
    if ( IsUserAuthenticated(h) ) userauthenticated = true;
    else userauthenticated = false;
    
    if (! Kernel32.INSTANCE.CloseHandle(h))
    {
      logLastErrorAndThrowException();
    }

    return inAccessToken;  
  }
      
  private HANDLE getAccessToken()
  {
    HANDLEByReference phToken = new HANDLEByReference();
    
    SecHandle sh = new SecHandle();
    sh.dwUpper = hContext.dwUpper;
    sh.dwLower = hContext.dwLower;

    int rc = Secur32bis.INSTANCE.QuerySecurityContextToken(sh, phToken);
    if (log.isDebugEnabled())
    {
      log.debug("QuerySecurityContextToken : " + rc); 
      if (rc != 0) logReturnCode(rc);
    }   
    if (rc != 0) logLastErrorAndThrowException();
    
    HANDLE hToken = phToken.getValue();
    return hToken;
  }
  
  private boolean isLocalGuest()
  {
    HANDLE h = getAccessToken();
    if ( IsUserAuthenticated(h) ) userauthenticated = true;
    else userauthenticated = false;
    
    if (! Kernel32.INSTANCE.CloseHandle(h))
    {
      logLastErrorAndThrowException();
    }

    return !userauthenticated;  
  }
  
  private void dispose()
  {
    if (isdisposed) return;

    if ( ! hContext.isInvalid())
    { 
      int rc = Secur32bis.INSTANCE.DeleteSecurityContext(hContext);
      hContext.dwLower.setValue(0);
      hContext.dwUpper.setValue(0);
      hContext = null;
      dwLowerContext = 0;
      dwUpperContext = 0;
      if (log.isDebugEnabled())
      {
        if (rc == -2146893055) log.debug("hContext disposed with return code: SEC_E_INVALID_HANDLE");
        else log.debug("hContext disposed: " +  rc);
      }
    }
    else 
    {
      if (log.isDebugEnabled()) log.debug(" dispose :hContext is invalid ");
    }

    if ( ! hCredential.isInvalid())
    {
      int rc = Secur32bis.INSTANCE.FreeCredentialsHandle(hCredential);
      hCredential.dwLower.setValue(0);
      hCredential.dwUpper.setValue(0);
      hCredential = null;
      dwLowerCredential = 0;
      dwUpperCredential = 0;
      if (log.isDebugEnabled())
      {
        if (rc == -2146893055) log.debug("hCredential disposed with return code: SEC_E_INVALID_HANDLE");
        else log.debug("hCredential disposed:" + rc);
      }
    }
    else 
    {
      if (log.isDebugEnabled()) log.debug(" dispose :hCredential is invalid");
    }

    if (log.isDebugEnabled())
    {
      if (remoteaddress != null) log.debug("***************!!!Context is disposed : " + remoteaddress);
      else log.debug("***************!!!Context is disposed with an unknown address");
    }
    if (windowscontext != null)
    {
      windowscontext.dwLowerCredential = dwLowerCredential;
      windowscontext.dwUpperCredential = dwUpperCredential;
      windowscontext.dwLowerContext = dwLowerContext; 
      windowscontext.dwUpperContext = dwUpperContext;
    }   
    isdisposed = true;
  }
  
  private void saveHandle()
  {
    dwUpperCredential = hCredential.getUpper();
    dwLowerCredential = hCredential.getLower();
    hCredential = null;
    dwUpperContext = hContext.getUpper();
    dwLowerContext = hContext.getLower();
    hContext = null;
    if (windowscontext != null)
    {
      windowscontext.dwLowerCredential = dwLowerCredential;
      windowscontext.dwUpperCredential = dwUpperCredential;
      windowscontext.dwLowerContext = dwLowerContext; 
      windowscontext.dwUpperContext = dwUpperContext;
    }       
    if (log.isDebugEnabled())  log.debug(
      "dwLowerCredential: " + dwLowerCredential +" ,dwUpperCredential: " + dwUpperCredential +
      " , dwLowerContext: " + dwLowerContext +" ,dwUpperContext: " + dwUpperContext); 
  }
 
}
